##################################################################
# File : /lua/ui/game/orders.lua
# Description : Order handling
# Author : GPG
##################################################################

local UIUtil = import('/lua/ui/uiutil.lua')
local LayoutHelpers = import('/lua/maui/layouthelpers.lua')
local Group = import('/lua/maui/group.lua').Group
local Bitmap = import('/lua/maui/bitmap.lua').Bitmap
local Grid = import('/lua/maui/grid.lua').Grid
local Checkbox = import('/lua/maui/checkbox.lua').Checkbox
local GameCommon = import('/lua/ui/game/gamecommon.lua')
local Button = import('/lua/maui/button.lua').Button
local Tooltip = import('/lua/ui/game/tooltip.lua')
local TooltipInfo = import('/lua/ui/help/tooltips.lua')
local Prefs = import('/lua/user/prefs.lua')
local Keymapping = import('/lua/keymap/DefaultKeyMap.lua').defaultKeyMap
local CM = import('/lua/ui/game/commandmode.lua')
local GameMain = import('/lua/ui/game/gamemain.lua')
local InGameUI = import('/lua/ui/game/InGameUI.lua')

local uiParent = nil
local icon_small_width = 24
local icon_small_height = 24

# Audio cues, used when ability specific entries do not exist in the ability
local DefaultOnEnterCue = 'Forge/UI/Hud/snd_ui_hud_generic_mouseover'               #PLACEHOLDER
local DefaultOnActivateCue = 'Forge/UI/Hud/snd_ui_hud_generic_click'           #PLACEHOLDER

# used by most orders, which start and stop a command mode, so they toggle on when pressed
# and toggle off when done
local function StandardOrderBehavior(self, modifiers)
    CM.StartCommandMode("order", {name=self._order})
end


# used by orders that happen immediately and don't change the command mode (ie the stop button)
local function MomentaryOrderBehavior(self, modifiers)
    IssueUnitCommand(GetSelectedUnits(), GetUnitCommandFromCommandCap(self._order))
end


local watch = {
    Thread = false,
    Orders = false,
    Hero = false,
    IsAssassin = false,
    PrevIsAssassin = false,
}

local Orders = {
    RULEUCC_Move = {
        Description = 'Move to a specificed location and do not engage enemies.',
        Name = 'Move',
        Icon = 'move',
        Command = 'UNITCOMMAND_FormMove',
        Behavior = StandardOrderBehavior,
        Slot = {1,1},
        Audio = {
            OnEnterCue = 'Forge/UI/Hud/snd_ui_hud_generic_mouseover',               #PLACEHOLDER
            OnActivateCue = 'Forge/UI/Hud/snd_ui_hud_generic_click',           #PLACEHOLDER
        },
    },

    RULEUCC_Attack = {
        Description = 'Attack a specific target.',
        Name = 'Attack',
        Icon = 'attack',
        Command = 'UNITCOMMAND_FormAttack',
        Behavior = StandardOrderBehavior,
        Slot = {2,1},
    },

    RULEUCC_AggressiveMove = {
        Description = 'Move to a specified location and engage enemies along the way.',
        Name = 'Aggressive Move',
        Icon = 'aggmove',
        Command = 'UNITCOMMAND_FormAggressiveMove',
        Behavior = StandardOrderBehavior,
        Slot = {1,2},
    },

    RULEUCC_Stop = {
        Description = 'Stop moving immediately.',
        Name = 'Stop',
        Icon = 'stop',
        Command = 'UNITCOMMAND_Stop',
        Behavior = MomentaryOrderBehavior,
        Slot = {2,2},
    },

    RULEUCC_Guard = {
        Description = 'Rally around a target and defend it from enemies.',
        Name = 'Assist',
        Icon = 'guard',
        Command = 'UNITCOMMAND_Guard',
        Behavior = StandardOrderBehavior,
        Slot = {3,1},
    },
}



################## TEMPORARY ##### THIS IS TEMPORARY ###################
local HybridOrders = {
    HybridSelectAll = {
        DisplayName = 'All Troops',
        Description = 'Select all of your available troops.\n\nControl-Q to select all available troops except your Demigod.',
        UISlot = {1,1},
        HotKey = 'Q',
        Icon = 'Unit_All',
        Audio = {
            OnEnterCue = 'Forge/UI/Hud/snd_ui_hud_generic_mouseover',               #PLACEHOLDER
            OnActivateCue = 'Forge/UI/Hud/snd_ui_hud_generic_click',           #PLACEHOLDER
        },
        OnClick = function(self,modifiers)
            if modifiers == 'ctrl' or IsKeyDown('Control') then
	               UISelectionByCategory("MOBILE -HERO", false, false, false, false)
            else
	               UISelectionByCategory("MOBILE", false, false, false, false)
	        end
        end,
    },
}
################## TEMPORARY ##### THIS IS TEMPORARY ###################




local GeneralsOrders = {
    GeneralSelectAll = {
        DisplayName = 'All Troops',
        Description = 'Select all of your available troops.\n\nControl-Q to select all available troops except your Demigod.',
        UISlot = {1,1},
        HotKey = 'Q',
        Icon = 'Unit_All',
        Audio = {
            OnEnterCue = 'Forge/UI/Hud/snd_ui_hud_generic_mouseover',               #PLACEHOLDER
            OnActivateCue = 'Forge/UI/Hud/snd_ui_hud_generic_click',           #PLACEHOLDER
        },
        OnClick = function(self,modifiers)
            if modifiers == 'ctrl' or IsKeyDown('Control') then
	               UISelectionByCategory("MOBILE -HERO", false, false, false, false)
            else
	               UISelectionByCategory("MOBILE", false, false, false, false)
	        end
        end,
    },

    GeneralSelectSoldiers = {
        DisplayName = 'Soldiers',
        Description = 'Select all of your available soldiers.',
        UISlot = {2,1},
        HotKey = 'W',
        Icon = 'Unit_Soldier',
        OnClick = function(self,modifiers)
            UISelectionByCategory("SOLDIER", false, false, false, false)
        end,
    },

    GeneralSelectArchers = {
        DisplayName = 'Archers',
        Description = 'Select all of your available archers.',
        UISlot = {3,1},
        HotKey = 'E',
        Icon = 'Unit_Archer',
        OnClick = function(self,modifiers)
            UISelectionByCategory("ARCHER", false, false, false, false)
        end,
    },

    GeneralSelectAngels = {
        DisplayName = 'Angels',
        Description = 'Select all of your available angels.',
        UISlot = {4,1},
        HotKey = 'R',
        Icon = 'Unit_Angel',
        OnClick = function(self,modifiers)
            UISelectionByCategory("ANGEL", false, false, false, false)
        end,
    },


    GeneralSelectSpecialX = {
        DisplayName = 'Special',
        Description = 'Select all of your available special units.',
        UISlot = {1,2},
        HotKey = 'Z',
        Icon = 'Unit_Special',
        OnClick = function(self,modifiers)
            UISelectionByCategory("SPECIALUNIT", false, false, false, false)
        end,
    },

    GeneralSelectPriests = {
        DisplayName = 'Priests',
        Description = 'Select all of your available priests.',
        UISlot = {2,2},
        HotKey = 'X',
        Icon = 'Unit_Priest',
        OnClick = function(self,modifiers)
            UISelectionByCategory("PRIEST", false, false, false, false)
        end,
    },

    GeneralSelectArtillery = {
        DisplayName = 'Catapultasaurii',
        Description = 'Select all of your available catapultasaurii.',
        UISlot = {3,2},
        HotKey = 'C',
        Icon = 'Unit_Catapult',
        OnClick = function(self,modifiers)
            UISelectionByCategory("CATAPULTASAURUS", false, false, false, false)
        end,
    },

    GeneralSelectGiants = {
        DisplayName = 'Giants',
        Description = 'Select all of your available giants.',
        UISlot = {4,2},
        HotKey = 'V',
        Icon = 'Unit_Giant',
        OnClick = function(self,modifiers)
            UISelectionByCategory("GIANT", false, false, false, false)
        end,
    },
}


# The grid doesn't own the ability buttons so remove them before they get destroyed.
local function RemoveAbilities(grid)
    local cols, rows = grid:GetDimensions()
    for c=1,cols do
        for r=1,rows do
            local item = grid:GetItem(c,r)
            if item then
                grid:RemoveItem(c,r,true)
                if not item.IsOrder and (watch.IsAssassin and watch.PrevIsAssassin) or Ability[item.AbilityName].UISpecialSlot then
                    item:SetEntity(nil)
                    item:SetParent(nil)
                end
            end
        end
    end
end


local function UseAbility(self,modifiers)
    self:UseAbility(modifiers)
end


local function AddOrder(orderInfo, slot, batchMode)
    batchMode = batchMode or false

    local button = nil
    if orderInfo.ability then
        button = GetAbilityButton(orderInfo.id)

        if not orderInfo.specialslot then
            button:SetParent(Controls.Abilities)
        else
            button:SetParent(Controls.Specials)
        end
        button:SetEntity(watch.Hero)
    end

    button.Width:Set(GameCommon.iconWidth)
    button.Height:Set(GameCommon.iconHeight)

    # set the info in to the data member for retrieval
    button._data = orderInfo

    # set disabled state if needed
    if orderInfo.disabled or orderInfo.passive then
        button:Disable(false, true)
    end

    # set up initial help text
    button._curHelpText = orderInfo.helpText

    # set up click handler
    if orderInfo.behavior then
        button.OnClick = orderInfo.behavior
    end

    # calculate row and column, remove old item, add new checkbox
    local row
    local col
    if orderInfo.ability then
        if not orderInfo.specialslot then
            col = slot[1]
            row = slot[2]
            if Controls.Abilities:GetItem(col,row) then
                local item = Controls.Abilities:GetItem(col,row)
                WARN('Ability Button collision: ', orderInfo.id, ' is clobbering ', item._data.id)
            end
        else

            col = orderInfo.specialslot[1]
            row = orderInfo.specialslot[2]
            if Controls.Specials:GetItem(col,row) then
                local item = Controls.Specials:GetItem(col,row)
                WARN('Specials Button collision: ', orderInfo.id, ' is clobbering ', item._data.id)
            end
        end
    else
        if not orderInfo.specialslot then
            local cols, rows = Controls.Abilities:GetDimensions()
            row = math.ceil(slot / cols)
            col = math.mod(slot - 1, cols) + 1
        else
            local cols, rows = Controls.Specials:GetDimensions()
            row = math.ceil(slot / cols)
            col = math.mod(slot - 1, cols) + 1
        end
    end

    if not orderInfo.specialslot then
        Controls.Abilities:DestroyItem(col, row, batchMode)
        Controls.Abilities:SetItem(button, col, row, batchMode)
    else
        Controls.Specials:DestroyItem(col, row, batchMode)
        Controls.Specials:SetItem(button, col, row, batchMode)
    end

    return button
end

#################################################
# Create the movement order buttons
#################################################

local function CreateOrderButton(order)
    local button = UIUtil.CreateButtonStdWithIcon(uiParent, '/buttons/round', '/game/orders/' .. order.Icon)
    button.Width:Set(GameCommon.iconWidth)
    button.Height:Set(GameCommon.iconHeight)

    button.Command = order.Command
    button.OnClick = order.Behavior

    button.mRolloverCue = order.Audio.OnEnterCue or DefaultOnEnterCue
    button.mClickCue = order.Audio.OnActivateCue or DefaultOnActivateCue

    return button
end


#################################################
# Create the General's troop order buttons
#################################################

local function CreateGeneralOrderButtons(generalOrders)
    for k, ord in generalOrders do
        local button = UIUtil.CreateButtonStdWithIcon(uiParent, '/buttons/round', '/GeneralsOrders/' .. ord.Icon)
        button.Width:Set(GameCommon.iconWidth)
        button.Height:Set(GameCommon.iconHeight)

        button.OnClick = ord.OnClick
        button.Description = ord.Description
 		button.Name = ord.DisplayName
 		button.hotkey = ord.HotKey

 		# This is to signify if this is an order rather than an ability; they behave differently as far
 		# as the UI is concerned for add/remove
 		button.IsOrder = true

        button.mRolloverCue = ord.Audio.OnEnterCue or DefaultOnEnterCue
        button.mClickCue = ord.Audio.OnActivateCue or DefaultOnActivateCue

        button.HandleEvent = function(self, event)
            if event.Type == 'MouseEnter' then
                button.tooltip = Tooltip.CreateExtendedTooltip(self, button.Name, button.Description)
                button.tooltip.Left:Set(function() return math.min(self.Left(), GetFrame(0).Right() - button.tooltip.Width()) end)
                button.tooltip.Top:Set(function() return self.Top() - button.tooltip.Height() end)
            elseif event.Type == 'MouseExit' then
                if button.tooltip then
                    button.tooltip:Destroy()
                    button.tooltip = nil
                end
            end
            return Button.HandleEvent(self, event)
        end

        button.hotkeylabel = UIUtil.CreateText(button, button.hotkey, 8, 'Arial Bold')
        button.hotkeylabel:SetDropShadow(true)
        button.hotkeylabel:SetColor('ffc3ebff')
        LayoutHelpers.AtHorizontalCenterIn(button.hotkeylabel, button)
        LayoutHelpers.AtBottomIn(button.hotkeylabel, button)

        button:SetParent(Controls.Abilities)
        if Controls.Abilities:GetItem(ord.UISlot[1], ord.UISlot[2]) then
            Controls.Abilities:DestroyItem(ord.UISlot[1], ord.UISlot[2])
        end
        Controls.Abilities:SetItem(button, ord.UISlot[1], ord.UISlot[2], true)

        if button.Name != 'All Troops' then
            button:Disable(false, true)
        end

    end

end


#################################################
# Create the Abilities buttons
#################################################

local function CreateAbilityButtons(abilities)
    local cols, rows = Controls.Abilities:GetDimensions()

    for name,data in abilities do
        local abilDef = Ability[name]
        if not abilDef then
            WARN('Unknown ability: ',name)
            continue
        end

        if abilDef.AbilityType == 'Quiet' or abilDef.AbilityType == 'Aura' then
            continue
        end

        if data.Removed or data.Hidden then
            continue
        end

        if abilDef.FromItem then
            continue
        end

        if not abilDef.UISlot and not abilDef.UISpecialSlot then
            WARN('Ability slot not specified for: ',name)
            WARN('Generating a temporary one')

            local col
            local row
            for row=1,rows do
                for col=1,cols do
                    if not Controls.Abilities:GetItem(col,row) then
                        # Fudge the row to get it to show up below normal hero abilities
                        abilDef.UISlot = {col, row}
                        break
                    end
                end
                if abilDef.UISlot or abilDef.UISpecialSlot then
                    break
                end
            end
        end

        local info = {
            helpText = abilDef.DisplayName,
            ability = true,
            bitmapId = abilDef.Icon or name,
            id = name,
            extraInfo = 4,
            specialslot = abilDef.UISpecialSlot,
        }

        if abilDef.AbilityType != 'Passive' then
            info.behavior = UseAbility
        else
            info.passive = true
        end

        local slot
        if abilDef.UISlot then
            slot = table.copy(abilDef.UISlot)
        elseif abilDef.UISpecialSlot then
            slot = table.copy(abilDef.UISpecialSlot)
        end
        #slot[2] = math.ceil(8 / cols) + slot[2]
        local cb = AddOrder(info,slot,true)
        cb._order = name

        # todo: set initial state
        #orderCheckboxMap[name] = cb
    end
end


#############################################
# Set up the grids to hold our buttons
#############################################

local function CreateAbilitiesGrid()
    Controls.Abilities = Grid(Controls, GameCommon.iconWidth, GameCommon.iconHeight, "Ability or Unit Orders")
    Controls.Abilities:AppendRows(2)
    Controls.Abilities:AppendCols(4)
    Controls.Abilities.Top:Set(function() return Controls.Top() end)
    Controls.Abilities.Width:Set(GameCommon.iconWidth * 4)
    Controls.Abilities.Height:Set(GameCommon.iconHeight * 2)
    #Controls.Abilities:SetSolidColor('aa0a0a0a')

    LayoutHelpers.LeftOf(Controls.Abilities,Controls.Specials, 10 + UIUtil.GetOffsets())
end

local function CreateSpecialsGrid()
    Controls.Specials = Grid(Controls, GameCommon.iconWidth, GameCommon.iconHeight, "Special Orders")
    Controls.Specials:AppendRows(2)
    Controls.Specials:AppendCols(2)
    Controls.Specials.Top:Set(function() return Controls.Top() end)
    Controls.Specials.Width:Set(GameCommon.iconWidth * 2)
    Controls.Specials.Height:Set(GameCommon.iconHeight * 2)
    #Controls.Specials:SetSolidColor('aa0a0a0a')

    LayoutHelpers.LeftOf(Controls.Specials,Controls.Orders, 10 + UIUtil.GetOffsets())

end

local function CreateOrdersGrid()
    Controls.Orders = Grid(Controls, GameCommon.iconWidth, GameCommon.iconHeight, "Move and Attack Orders")
    Controls.Orders:AppendRows(2)
    Controls.Orders:AppendCols(3)
    #Controls.Orders.Width:Set(icon_small_width * 2)
    #Controls.Orders.Height:Set(icon_small_height * 3)
    Controls.Orders.Width:Set(GameCommon.iconWidth * 3)
    Controls.Orders.Height:Set(GameCommon.iconHeight * 2)
    for k,v in Orders do
        local button = CreateOrderButton(v)
        button:SetParent(Controls.Orders)
        button._order = k
        button.Name = v.Name
        button.Description = v.Description
        button.HandleEvent = function(self, event)
            if event.Type == 'MouseEnter' then
	            Controls.Orders.tooltip = Tooltip.CreateExtendedTooltip(self, button.Name, button.Description)
                Controls.Orders.tooltip.Left:Set(function() return math.min(self.Left(), GetFrame(0).Right() - Controls.Orders.tooltip.Width()) end)
                Controls.Orders.tooltip.Top:Set(function() return self.Top() - Controls.Orders.tooltip.Height() end)
            elseif event.Type == 'MouseExit' then
                if Controls.Orders.tooltip then
                    Controls.Orders.tooltip:Destroy()
                    Controls.Orders.tooltip = nil
                end
            end
            return Button.HandleEvent(self, event)
        end

        Controls.Orders:SetItem(button,v.Slot[1],v.Slot[2],true)

        --[[
        if v.Slot[1] != 3 then
           Controls.Orders:SetItem(button,v.Slot[1],v.Slot[2],true)
        else
           LayoutHelpers.RightOf(button, Controls.Orders)
           LayoutHelpers.AtVerticalCenterIn(button, Controls.Orders)
        end
        ]]--
    end
    Controls.Orders:EndBatch()
    LayoutHelpers.AtRightTopIn(Controls.Orders,Controls)
end


local function SetAvailableOrders(availableOrders, unit, abilities)

    local WatchAbilities = function(initialAbilities)
        local unit = watch.Hero
        local id = unit:GetEntityId()
        local abilities = initialAbilities
        while true do
            if IsDestroyed(watch.Hero) then
                SetAvailableOrders({},nil,nil)
                return
            elseif abilities != EntityData[id].Abilities then
                abilities = EntityData[id].Abilities
                SetAvailableOrders(watch.Orders, watch.Hero, abilities)
                return
            end
            WaitFrames(1)
        end
    end

    if watch.Thread then
        KillThread(watch.Thread)
        watch.Thread = false
    end

    # look for a hero
    watch.Hero = false
    if unit and not IsDestroyed(unit) then
        if EntityCategoryContains(categories.USEABILITY,unit) or EntityCategoryContains(categories.HERO,unit) then
            watch.Hero = unit
            watch.PrevIsAssassin = watch.IsAssassin
            watch.IsAssassin = true
        end
    end

    if watch.Hero then
        watch.Orders = availableOrders
        watch.Thread = ForkThread( WatchAbilities, abilities )
    end

    if watch.IsAssassin then
        RemoveAbilities(Controls.Abilities)
        Controls.Abilities:DestroyAllItems(true)
    end

    #### HACK TO TEST ####
    if watch.IsHybrid then
        CreateGeneralOrderButtons(HybridOrders)
    end

    RemoveAbilities(Controls.Specials)
    Controls.Specials:DestroyAllItems(true)

    if not watch.IsAssassin then
        if abilities and not table.empty(abilities) then
            if unit and not IsDestroyed(unit) then
                Controls.Abilities:DestroyAllItems(true)
            end
            CreateGeneralOrderButtons(GeneralsOrders)
        end
    end

    if abilities and not table.empty(abilities) then
        CreateAbilityButtons(abilities)
    end

    Controls.Abilities:EndBatch()
    Controls.Specials:EndBatch()

end

function UseAbilitySlot(col,row,modifier)
    local cols, rows = Controls.Abilities:GetDimensions()

    if row <= rows and col <= cols then
        local item = Controls.Abilities:GetItem(col,row)
        if item then
            item:OnClick(modifier)
        end
    end
end


function UseSpecialSlot(col,row)
    local cols, rows = Controls.Specials:GetDimensions()

    if row <= rows and col <= cols then
        local item = Controls.Specials:GetItem(col,row)
        if item then
            item:OnClick()
        end
    end
end

local function OnFocusArmyHeroChange(hero)

    if not hero then
        SetAvailableOrders({},nil,nil)
        return
    end

    local availableOrders = GetAvailableOrders(hero)
    local abilities = {}
    local data = EntityData[hero:GetEntityId()]

    if data and data.Abilities then
        abilities = data.Abilities
    end
    SetAvailableOrders(availableOrders, hero, abilities)

end

# This gives us the units the general has so we can gray out unit buttons accordingly
local function UpdateScoreData()

    if not watch.IsAssassin then

        if Sync.Score then
            local currentScores = Sync.Score
            local scoreData = currentScores[GetFocusArmy()]

                for unitGroup, unitData in GeneralsOrders do
                    if unitData.DisplayName != 'All Troops' then
                        local unitname = unitData.DisplayName
                        local c, r = unitData.UISlot[1], unitData.UISlot[2]
                        local btn = Controls.Abilities:GetItem(c, r)

                        if btn then
                            if scoreData[unitname] != 0 and btn:IsDisabled() then
                                btn:Enable()
                            elseif scoreData[unitname] == 0 and not btn:IsDisabled() then
                                btn:Disable(false, true)
                            end
                        end
                    end
                end

        end

    end
end

function Create(parent)
    uiParent = parent

    # master group that will contain the orders grid, the specials grid, and the abilities/units grid
    Controls = Group(parent, "Master Orders Group")
    Controls.Width:Set(300)
    Controls.Height:Set(80)

    InGameUI.OnFocusArmyHeroChange:Add(OnFocusArmyHeroChange)
    SyncCallback:Add(UpdateScoreData)

    CreateOrdersGrid()
    CreateSpecialsGrid()
    CreateAbilitiesGrid()

    return Controls
end
